﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace CSharpExt.IO
{
    /// <summary>
    /// Questa classe ha il compito di permettere di leggere e scrivere dei file
    /// composti da piu' 'chunck' e per cui e' stato definito la dimensione
    /// massima di ogni chunk e il numero totale di chunk.
    /// 
    /// I chunk vengono nominati nel seguente modo:
    /// 
    ///     name.ext        // name[0].ext
    ///     name[1].ext
    ///     ...
    ///     name[N].ext
    /// 
    /// Quando viene creato un nuovo chunk, i file vengono rinominati nel seguente modo:
    /// 
    ///     name[N].ext     -> deleted
    ///     name[N-1].ext   -> name[N].ext
    ///     ...
    ///     name.ext        -> name[1].ext
    /// 
    /// Corrispondentemente, la lettura deve essere eseguita leggendo i chunk all'incontrario
    /// 
    ///     name[N].ext
    ///     name[N-1].ext
    ///     ...
    ///     name.ext
    /// 
    /// </summary>
    public class FileSplitter
    {
        #region Static Methods

        public static Stream OpenRead(String path)
        {
            return Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
        }

        public static Stream OpenRead(String path, int maxChunks)
        {
            return Open(path, maxChunks, FileMode.Open, FileShare.None);
        }

        public static Stream OpenWrite(String path)
        {
            return OpenWrite(path, 0, 0);
        }

        public static Stream OpenWrite(String path, int chunkSize, int maxChunks)
        {
            return Open(path, chunkSize, maxChunks, FileMode.CreateNew, FileShare.None);
        }


        public static Stream Open(String path, FileMode mode, FileAccess access, FileShare share)
        {
            if(access == FileAccess.Read)
            {
                return Open(path, 0, mode, share);
            }
            else
            {
                return Open(path, 0, 0, mode, share);
            }
        }


        public static Stream Open(String path, int maxChunks, FileMode mode, FileShare share)
        {
            return (new InputStream(path, mode, share)
            {
                MaxChunks = maxChunks
            }).Init();
        }

        public static Stream Open(String path, int chunkSize, int maxChunks, FileMode mode, FileShare share)
        {
            return (new OutputStream(path, mode, share)
                        {
                            ChunkSize = chunkSize, 
                            MaxChunks = maxChunks
                        }).Init();
        }

        #endregion
    }

    class OutputStream : Stream
    {
        #region Private Data

        private String _path;
        private int _chunkSize;
        private int _maxChunks;

        private FileMode _mode;
        private FileShare _share;

        private String _directory;
        private String _name;
        private String _extension;

        private Stream _output;
        private int _writed;
        private byte[] _byte = new byte[1];

        #endregion

        #region Constructor

        internal OutputStream(String path, FileMode mode, FileShare share)
        {
            _path = path;
            _mode = mode;
            _share = share;
        }

        internal OutputStream Init()
        {
            splitPath();
            rename();
            open();
            return this;
        }

        #endregion

        #region Special Properties

        public int ChunkSize
        {
            set { _chunkSize = value; }
        }

        public int MaxChunks
        {
            set { _maxChunks = value; }
        }

        #endregion

        #region Properties

        public override bool CanRead
        {
            get { return false; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        #endregion

        #region Operations

        public override void WriteByte(byte value)
        {
            _byte[0] = value;
            Write(_byte, 0, 1);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _output.Write(buffer, offset, count);
            _writed += count;

            if (_chunkSize != 0 && _writed > _chunkSize)
            {
                close();
                rename();
                open();
            }
        }

        public override void Flush()
        {
            _output.Flush();
        }

        public override void Close()
        {
            if(_output != null)
            {
                _output.Close();
                _output = null;
            }
        }

        #endregion

        #region Unsupported Operations

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        public override long Position
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Implementation

        private void splitPath()
        {
            _directory = Path.GetDirectoryName(_path);
            _name = Path.GetFileName(_path);
            _extension = Path.GetExtension(_path);
            _name = _name.Substring(0, _name.IndexOf(_extension));
        }

        private void rename()
        {
            int index = findIndex();
            while(index >= _maxChunks)
            {
                String path = makeName(index--);
                if (File.Exists(path))
                    File.Delete(path);
            }
            while(index >= 0)
            {
                String nextPath = makeName(index + 1);
                String prevPath = makeName(index--);

                if (File.Exists(nextPath))
                    File.Delete(nextPath);
                if (File.Exists(prevPath))
                    File.Move(prevPath, nextPath);
            }
        }

        private void open()
        {
            _writed = 0;
            _output = new FileStream(_path, _mode, FileAccess.Write, _share);
        }

        private void close()
        {
            _output.Close();
        }

        private int findIndex()
        {
            for(int i=1; ; ++i)
            {
                String path = makeName(i);
                if (!File.Exists(path))
                    return i - 1;
            }
        }

        private String makeName(int i)
        {
            if(i == 0)
                return _path;
            else
                return Path.Combine(_directory, String.Format("{0}[{1}]{2}", _name, i, _extension));
        }

        #endregion
    }

    class InputStream : Stream
    {
        #region Private Data

        private String _path;
        private int _maxChunks;

        private FileMode _mode;
        private FileShare _share;

        private String _directory;
        private String _name;
        private String _extension;

        private String _current;
        private int _index;

        private int _length;
        private int _readed;
        private Stream _input;

        private byte[] _byte = new byte[1];

        #endregion

        #region Constructor

        public InputStream(String path, FileMode mode, FileShare share)
        {
            _path = path;
            _mode = mode;
            _share = share;
        }

        internal InputStream Init()
        {
            splitPath();
            findIndex();
            open();
            return this;
        }

        #endregion

        #region Properties

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        #endregion

        #region Special Properties

        public int MaxChunks
        {
            set { _maxChunks = value; }
        }

        #endregion

        #region Operations

        public override int ReadByte()
        {
            int readed = Read(_byte, 0, 1);
            if (readed == 1)
                return _byte[0];
            else
                return -1;
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (_index == -1)
                return 0;

            if(count <= _length - _readed)
            {
                int readed = _input.Read(buffer, offset, count);
                _readed += readed;
                return readed;
            }
            if(_length - _readed > 0)
            {
                int readed = _input.Read(buffer, offset, count);
                _readed += readed;
                return readed;
            }
            if(_index == 0)
            {
                return 0;
            }

            {
                close();
                prevIndex();
                open();

                return Read(buffer, offset, count);
            }
        }

        public override void Flush()
        {
            _input.Flush();
        }

        public override void Close()
        {
            if(_input != null)
            {
                _input.Close();
                _input = null;
            }
        }

        #endregion

        #region Unsupported Operations

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        public override long Position
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Implementation

        private void splitPath()
        {
            _directory = Path.GetDirectoryName(_path);
            _name = Path.GetFileName(_path);
            _extension = Path.GetExtension(_path);
            _name = _name.Substring(0, _name.IndexOf(_extension));
        }

        private void findIndex()
        {
            for (int i = 1; _maxChunks == 0 || i < _maxChunks ; ++i)
            {
                String path = makeName(i);
                if (!File.Exists(path))
                {
                    _index = i - 1;
                    return;
                }
            }
        }

        private void prevIndex()
        {
            --_index;
        }

        private void open()
        {
            _current = makeName(_index);
            _readed = 0;
            _length = (int) new FileInfo(_current).Length;
            _input = new FileStream(_current, _mode, FileAccess.Read, _share);
        }

        private void close()
        {
            _input.Close();
        }

        private String makeName(int i)
        {
            if (i == 0)
                return _path;
            else
                return Path.Combine(_directory, String.Format("{0}[{1}]{2}", _name, i, _extension));
        }

        #endregion
    }

}
